package com.imy.virtual.server.pm;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.imy.virtual.client.core.InstallStrategy;
import com.imy.virtual.client.core.VirtualCore;
import com.imy.virtual.client.env.VirtualRuntime;
import com.imy.virtual.helper.ArtDexOptimizer;
import com.imy.virtual.helper.collection.IntArray;
import com.imy.virtual.helper.compat.NativeLibraryHelperCompat;
import com.imy.virtual.helper.utils.ArrayUtils;
import com.imy.virtual.helper.utils.FileUtils;
import com.imy.virtual.helper.utils.VLog;
import com.imy.virtual.os.VEnvironment;
import com.imy.virtual.os.VUserHandle;
import com.imy.virtual.remote.InstallResult;
import com.imy.virtual.remote.InstalledAppInfo;
import com.imy.virtual.server.accounts.VAccountManagerService;
import com.imy.virtual.server.am.BroadcastSystem;
import com.imy.virtual.server.am.UidSystem;
import com.imy.virtual.server.am.VActivityManagerService;
import com.imy.virtual.server.interfaces.IAppManager;
import com.imy.virtual.server.interfaces.IAppRequestListener;
import com.imy.virtual.server.interfaces.IPackageObserver;
import com.imy.virtual.server.pm.parser.PackageParserEx;
import com.imy.virtual.server.pm.parser.VPackage;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import dalvik.system.DexFile;

public class VAppManagerService implements IAppManager {
  private static final String TAG = VAppManagerService.class.getSimpleName();
  private static final AtomicReference<VAppManagerService> sService = new AtomicReference<>();
  private final UidSystem mUidSystem = new UidSystem();
  private final PackagePersistenceLayer mPersistenceLayer = new PackagePersistenceLayer(this);
  private final Set<String> mVisibleOutsidePackages = new HashSet<>();
  private boolean mBooting;
  private RemoteCallbackList<IPackageObserver> mRemoteCallbackList = new RemoteCallbackList<>();
  private IAppRequestListener mAppRequestListener;

  public static VAppManagerService get() {
    return sService.get();
  }

  public static void systemReady() {
    VEnvironment.systemReady();
    VAppManagerService instance = new VAppManagerService();
    instance.mUidSystem.initUidList();
    sService.set(instance);
  }

  public boolean isBooting() {
    return mBooting;
  }

  @Override
  public void scanApps() {
    if (mBooting) {
      return;
    }
    synchronized (this) {
      mBooting = true;
      mPersistenceLayer.read();
      PrivilegeAppOptimizer.get().performOptimizeAllApps();
      mBooting = false;
    }
  }

  private void cleanUpResidualFiles(PackageSetting ps) {
    File dataAppDir = VEnvironment.getDataAppPackageDirectory(ps.packageName);
    FileUtils.deleteDir(dataAppDir);
    for (int userId : VUserManagerService.get().getUserIds()) {
      FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, ps.packageName));
    }
  }

  synchronized void loadPackage(PackageSetting setting) {
    if (!loadPackageInnerLocked(setting)) {
      cleanUpResidualFiles(setting);
    }
  }

  private boolean loadPackageInnerLocked(PackageSetting ps) {
    if (ps.dependSystem) {
      if (!VirtualCore.get().isOutsideInstalled(ps.packageName)) {
        return false;
      }
    }
    File cacheFile = VEnvironment.getPackageCacheFile(ps.packageName);
    VPackage pkg = null;
    try {
      pkg = PackageParserEx.readPackageCache(ps.packageName);
    } catch (Throwable e) {
      e.printStackTrace();
    }
    if (pkg == null || pkg.packageName == null) {
      return false;
    }
    chmodPackageDictionary(cacheFile);
    PackageCacheManager.put(pkg, ps);
    BroadcastSystem.get().startApp(pkg);
    return true;
  }

  @Override
  public boolean isOutsidePackageVisible(String pkg) {
    return pkg != null && mVisibleOutsidePackages.contains(pkg);
  }

  @Override
  public void addVisibleOutsidePackage(String pkg) {
    if (pkg != null) {
      mVisibleOutsidePackages.add(pkg);
    }
  }

  @Override
  public void removeVisibleOutsidePackage(String pkg) {
    if (pkg != null) {
      mVisibleOutsidePackages.remove(pkg);
    }
  }

  @Override
  public InstallResult installPackage(String path, int flags, boolean dex2oat) {
    return installPackage(path, flags, true, dex2oat);
  }

  public synchronized InstallResult installPackage(String path, int flags, boolean notify, boolean dex2oat) {
    Log.i(TAG, "安装开始");
    long installTime = System.currentTimeMillis();
    if (path == null) {
      return InstallResult.makeFailure("path = NULL");
    }
    File packageFile = new File(path);
    VLog.i("这个是apk 的存在系统中的地址 packageFile:" + packageFile.getAbsolutePath());
    if (!packageFile.exists() || !packageFile.isFile()) {
      return InstallResult.makeFailure("Package File is not exist.");
    }
    VPackage pkg = null;
    try {
      pkg = PackageParserEx.parsePackage(packageFile);
    } catch (Throwable e) {
      e.printStackTrace();
      VLog.i("解析包异常");
      VLog.printThrowable(e);
    }
    if (pkg == null || pkg.packageName == null) {
      return InstallResult.makeFailure("Unable to parse the package.");
    }
    InstallResult res = new InstallResult();
    res.packageName = pkg.packageName;
    VPackage existOne = PackageCacheManager.get(pkg.packageName);
    PackageSetting existSetting = existOne != null ? (PackageSetting) existOne.mExtras : null;
    if (existOne != null) {
      if ((flags & InstallStrategy.IGNORE_NEW_VERSION) != 0) {
        res.isUpdate = true;
        return res;
      }
      if (!canUpdate(existOne, pkg, flags)) {
        return InstallResult.makeFailure("Not allowed to update the package.");
      }
      res.isUpdate = true;
    }
    File appDir = VEnvironment.getDataAppPackageDirectory(pkg.packageName);
    File libDir = new File(appDir, "lib");
    if (res.isUpdate) {
      FileUtils.deleteDir(libDir);
      VEnvironment.getOdexFile(pkg.packageName).delete();
      VActivityManagerService.get().killAppByPkg(pkg.packageName, VUserHandle.USER_ALL);
    }
    if (!libDir.exists() && !libDir.mkdirs()) {
      return InstallResult.makeFailure("Unable to create lib dir.");
    }
    boolean dependSystem = (flags & InstallStrategy.DEPEND_SYSTEM_IF_EXIST) != 0
        && VirtualCore.get().isOutsideInstalled(pkg.packageName);
    if (existSetting != null && existSetting.dependSystem) {
      dependSystem = false;
    }
    Log.i(TAG, "dependSystem:" + dependSystem);
    VLog.i("安装apk ,这里拷贝so 到指定目录;  path=" + path + "  ;libDir path=" + libDir.getAbsolutePath());
    int i = NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);

    if (i == NativeLibraryHelperCompat.ONLY_SUPPORT_64) {
      return InstallResult.makeFailure("current vm is 32bit but apk so is only support 64bit",
          InstallResult.ONLY_SUPPORT_64BIT, pkg.packageName);
    }
    //判断是否是纯64的
    Log.i(TAG, "after copyNativeBinaries");
    if (!dependSystem) {
      File privatePackageFile = new File(appDir, "base.apk");
      File parentFolder = privatePackageFile.getParentFile();
      if (!parentFolder.exists() && !parentFolder.mkdirs()) {
        VLog.w(TAG, "Warning: unable to create folder : " + privatePackageFile.getPath());
      } else if (privatePackageFile.exists() && !privatePackageFile.delete()) {
        VLog.w(TAG, "Warning: unable to delete file : " + privatePackageFile.getPath());
      }
      try {
        FileUtils.copyFile(packageFile, privatePackageFile);
      } catch (IOException e) {
        privatePackageFile.delete();
        return InstallResult.makeFailure("Unable to copy the package file.");
      }
      packageFile = privatePackageFile;
    }
    if (existOne != null) {
      PackageCacheManager.remove(pkg.packageName);
    }
    chmodPackageDictionary(packageFile);
    PackageSetting ps;
    if (existSetting != null) {
      ps = existSetting;
    } else {
      ps = new PackageSetting();
    }
    ps.dependSystem = dependSystem;
    ps.apkPath = packageFile.getPath();
    ps.libPath = libDir.getPath();
    ps.packageName = pkg.packageName;
    ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
    if (res.isUpdate) {
      ps.lastUpdateTime = installTime;
    } else {
      ps.firstInstallTime = installTime;
      ps.lastUpdateTime = installTime;
      for (int userId : VUserManagerService.get().getUserIds()) {
        boolean installed = userId == 0;
        ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
      }
    }
    PackageParserEx.savePackageCache(pkg);
    PackageCacheManager.put(pkg, ps);
    mPersistenceLayer.save();
    long l5 = System.currentTimeMillis();
    if (!dependSystem) {
      boolean runDexOpt = false;
      if (VirtualRuntime.isArt()) {
        try {
          /**
           * DEX2oat 由于在系统中copy过来的 已经进行过dex2Oat 所以copy的不用执行此方法
           * 但是未安装的必须执行一次这个 该过程非常耗时
           */

          ArtDexOptimizer.interpretDex2Oat(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath());

        } catch (IOException e) {
          e.printStackTrace();
          runDexOpt = true;
        }
      } else {
        runDexOpt = true;
      }
      if (runDexOpt) {
        try {
          DexFile.loadDex(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath(), 0).close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    long l3 = System.currentTimeMillis();
    Log.i(TAG, "interpretDex2Oat耗时:" + (l3 - l5));
    BroadcastSystem.get().startApp(pkg);
    long l4 = System.currentTimeMillis();
    Log.i(TAG, "startApp耗时:" + (l4 - l3));

    long l1 = System.currentTimeMillis();
    if (notify) {
      notifyAppInstalled(ps, -1);
    }
    long l2 = System.currentTimeMillis();
    Log.i(TAG, "notifyAppInstalled耗时:" + (l2 - l1));
    res.isSuccess = true;
    long l = System.currentTimeMillis();
    Log.i(TAG, "安装成功  耗时:" + (l - installTime));
    return res;
  }

  @Override
  public synchronized boolean installPackageAsUser(int userId, String packageName) {
    if (VUserManagerService.get().exists(userId)) {
      PackageSetting ps = PackageCacheManager.getSetting(packageName);
      VLog.i(" package 的缓存 VPackage 存储着一个 App 的所有信息： " + ps);
      if (ps != null) {
        if (!ps.isInstalled(userId)) {
          ps.setInstalled(userId, true);
          notifyAppInstalled(ps, userId);
          mPersistenceLayer.save();
          return true;
        }
      }
    }
    return false;
  }

  private void chmodPackageDictionary(File packageFile) {
    try {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (FileUtils.isSymlink(packageFile)) {
          return;
        }
        FileUtils.chmod(packageFile.getParentFile().getAbsolutePath(), FileUtils.FileMode.MODE_755);
        FileUtils.chmod(packageFile.getAbsolutePath(), FileUtils.FileMode.MODE_755);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private boolean canUpdate(VPackage existOne, VPackage newOne, int flags) {
    if ((flags & InstallStrategy.COMPARE_VERSION) != 0) {
      if (existOne.mVersionCode < newOne.mVersionCode) {
        return true;
      }
    }
    if ((flags & InstallStrategy.TERMINATE_IF_EXIST) != 0) {
      return false;
    }
    if ((flags & InstallStrategy.UPDATE_IF_EXIST) != 0) {
      return true;
    }
    return false;
  }

  @Override
  public synchronized boolean uninstallPackage(String packageName) {
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    if (ps != null) {
      uninstallPackageFully(ps);
      return true;
    }
    return false;
  }

  @Override
  public synchronized boolean uninstallPackageAsUser(String packageName, int userId) {
    if (!VUserManagerService.get().exists(userId)) {
      return false;
    }
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    if (ps != null) {
      int[] userIds = getPackageInstalledUsers(packageName);
      if (!ArrayUtils.contains(userIds, userId)) {
        return false;
      }
      if (userIds.length == 1) {
        uninstallPackageFully(ps);
      } else {
        VActivityManagerService.get().killAppByPkg(packageName, userId);
        ps.setInstalled(userId, false);
        notifyAppUninstalled(ps, userId);
        mPersistenceLayer.save();
        FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, packageName));
      }
      return true;
    }
    return false;
  }

  private void uninstallPackageFully(PackageSetting ps) {
    String packageName = ps.packageName;
    try {
      BroadcastSystem.get().stopApp(packageName);
      VActivityManagerService.get().killAppByPkg(packageName, VUserHandle.USER_ALL);
      VEnvironment.getPackageResourcePath(packageName).delete();
      FileUtils.deleteDir(VEnvironment.getDataAppPackageDirectory(packageName));
      VEnvironment.getOdexFile(packageName).delete();
      for (int id : VUserManagerService.get().getUserIds()) {
        FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(id, packageName));
      }
      PackageCacheManager.remove(packageName);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      notifyAppUninstalled(ps, -1);
    }
  }

  @Override
  public int[] getPackageInstalledUsers(String packageName) {
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    if (ps != null) {
      IntArray installedUsers = new IntArray(5);
      int[] userIds = VUserManagerService.get().getUserIds();
      for (int userId : userIds) {
        if (ps.readUserState(userId).installed) {
          installedUsers.add(userId);
        }
      }
      return installedUsers.getAll();
    }
    return new int[0];
  }

  @Override
  public List<InstalledAppInfo> getInstalledApps(int flags) {
    List<InstalledAppInfo> infoList = new ArrayList<>(getInstalledAppCount());
    for (VPackage p : PackageCacheManager.PACKAGE_CACHE.values()) {
      PackageSetting setting = (PackageSetting) p.mExtras;
      infoList.add(setting.getAppInfo());
    }
    return infoList;
  }

  @Override
  public List<InstalledAppInfo> getInstalledAppsAsUser(int userId, int flags) {
    List<InstalledAppInfo> infoList = new ArrayList<>(getInstalledAppCount());
    for (VPackage p : PackageCacheManager.PACKAGE_CACHE.values()) {
      PackageSetting setting = (PackageSetting) p.mExtras;
      boolean visible = setting.isInstalled(userId);
      if ((flags & VirtualCore.GET_HIDDEN_APP) == 0 && setting.isHidden(userId)) {
        visible = false;
      }
      if (visible) {
        infoList.add(setting.getAppInfo());
      }
    }
    return infoList;
  }

  @Override
  public int getInstalledAppCount() {
    return PackageCacheManager.PACKAGE_CACHE.size();
  }

  @Override
  public boolean isAppInstalled(String packageName) {
    return packageName != null && PackageCacheManager.PACKAGE_CACHE.containsKey(packageName);
  }

  @Override
  public boolean isAppInstalledAsUser(int userId, String packageName) {
    if (packageName == null || !VUserManagerService.get().exists(userId)) {
      return false;
    }
    PackageSetting setting = PackageCacheManager.getSetting(packageName);
    if (setting == null) {
      return false;
    }
    return setting.isInstalled(userId);
  }

  private void notifyAppInstalled(PackageSetting setting, int userId) {
    final String pkg = setting.packageName;
    int N = mRemoteCallbackList.beginBroadcast();
    while (N-- > 0) {
      try {
        if (userId == -1) {
          sendInstalledBroadcast(pkg);
          mRemoteCallbackList.getBroadcastItem(N).onPackageInstalled(pkg);
          mRemoteCallbackList.getBroadcastItem(N).onPackageInstalledAsUser(0, pkg);
        } else {
          mRemoteCallbackList.getBroadcastItem(N).onPackageInstalledAsUser(userId, pkg);
        }
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    }
    mRemoteCallbackList.finishBroadcast();
    VAccountManagerService.get().refreshAuthenticatorCache(null);
  }

  private void notifyAppUninstalled(PackageSetting setting, int userId) {
    final String pkg = setting.packageName;
    int N = mRemoteCallbackList.beginBroadcast();
    while (N-- > 0) {
      try {
        if (userId == -1) {
          sendUninstalledBroadcast(pkg);
          mRemoteCallbackList.getBroadcastItem(N).onPackageUninstalled(pkg);
          mRemoteCallbackList.getBroadcastItem(N).onPackageUninstalledAsUser(0, pkg);
        } else {
          mRemoteCallbackList.getBroadcastItem(N).onPackageUninstalledAsUser(userId, pkg);
        }
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    }
    mRemoteCallbackList.finishBroadcast();
    //VAccountManagerService.get().refreshAuthenticatorCache(null);
  }

  private void sendInstalledBroadcast(String packageName) {
    Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
    intent.setData(Uri.parse("package:" + packageName));
    VActivityManagerService.get().sendBroadcastAsUser(intent, VUserHandle.ALL);
  }

  private void sendUninstalledBroadcast(String packageName) {
    Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
    intent.setData(Uri.parse("package:" + packageName));
    VActivityManagerService.get().sendBroadcastAsUser(intent, VUserHandle.ALL);
  }

  @Override
  public void registerObserver(IPackageObserver observer) {
    try {
      mRemoteCallbackList.register(observer);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  @Override
  public void unregisterObserver(IPackageObserver observer) {
    try {
      mRemoteCallbackList.unregister(observer);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  @Override
  public IAppRequestListener getAppRequestListener() {
    return mAppRequestListener;
  }

  @Override
  public void setAppRequestListener(final IAppRequestListener listener) {
    this.mAppRequestListener = listener;
    if (listener != null) {
      try {
        listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
          @Override
          public void binderDied() {
            listener.asBinder().unlinkToDeath(this, 0);
            VAppManagerService.this.mAppRequestListener = null;
          }
        }, 0);
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public void clearAppRequestListener() {
    this.mAppRequestListener = null;
  }

  @Override
  public InstalledAppInfo getInstalledAppInfo(String packageName, int flags) {
    synchronized (PackageCacheManager.class) {
      if (packageName != null) {
        PackageSetting setting = PackageCacheManager.getSetting(packageName);
        VLog.i("获取安装的apk PackageSetting ：" + setting);
        if (setting != null) {
          return setting.getAppInfo();
        }
      }
      return null;
    }
  }

  public boolean isPackageLaunched(int userId, String packageName) {
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    return ps != null && ps.isLaunched(userId);
  }

  public void setPackageHidden(int userId, String packageName, boolean hidden) {
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    if (ps != null && VUserManagerService.get().exists(userId)) {
      ps.setHidden(userId, hidden);
      mPersistenceLayer.save();
    }
  }

  public int getAppId(String packageName) {
    PackageSetting setting = PackageCacheManager.getSetting(packageName);
    return setting != null ? setting.appId : -1;
  }

  void restoreFactoryState() {
    VLog.w(TAG, "Warning: Restore the factory state...");
    VEnvironment.getDalvikCacheDirectory().delete();
    VEnvironment.getUserSystemDirectory().delete();
    VEnvironment.getDataAppDirectory().delete();
  }

  public void savePersistenceData() {
    mPersistenceLayer.save();
  }
}
